package chagine.core.config;

import chagine.core.current.CurrentEnv;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 配置异步任务线程
 */
@Slf4j
@EnableAsync
@Configuration
public class AsyncTaskConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        threadPool.setCorePoolSize(processors * 2); //核心线程数
        threadPool.setMaxPoolSize(processors * 4);  //最大线程数
        threadPool.setQueueCapacity(10000); //队列大小
        threadPool.setKeepAliveSeconds(300); //线程最大空闲时间
        threadPool.setWaitForTasksToCompleteOnShutdown(true); // 等待任务在关机时完成--表明等待所有线程执行完
        threadPool.setAwaitTerminationSeconds(120);// 等待时间 （默认为0，此时立即停止），并等待120秒后强制停止
        threadPool.setThreadNamePrefix("Async-"); ////指定用于新创建的线程名称的前缀。
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
        setLogHandler(threadPool);//配置日志策略
        threadPool.initialize();// 初始化线程
        return threadPool;
    }

    /**
     * 日志策略选择
     * 1.TaskDecorator
     * 2.AsyncUncaughtExceptionHandler
     * 3.AsyncMonitor
     */
    public static int ASYNC_TASK_LOG_SELECTION = 3;

    //配置日志策略
    private void setLogHandler(ThreadPoolTaskExecutor threadPool) {
        AsyncMonitor.THREAD_POOL_TASK_EXECUTOR = threadPool;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            HttpRequestInfo httpRequestInfo = RequestContext.get(false);

            System.out.println("request_id[" + CurrentEnv.getContent().getId() + "]");
            System.out.println("——————————————————");
            System.out.println("|async error info|");
            System.out.println("——————————————————————————————————————————————————————————————————————————————");
            System.out.println("origin_protocol: " + httpRequestInfo.getProtocol());
            System.out.println("origin_method  : " + httpRequestInfo.getMethod());
            System.out.println("origin_uri     : " + httpRequestInfo.getRequestURI());
            System.out.println("origin_queryStr: " + httpRequestInfo.getQueryString());
            System.out.println("——————————————————————————————————————————————————————————————————————————————");
            System.out.println("class#method: " + method.getDeclaringClass().getName() + "#" + method.getName());
            System.out.println("type        : " + ex.getClass().getName());
            System.out.println("message     : " + ex.getMessage());
            System.out.println("——————————————————————————————————————————————————————————————————————————————");
            System.out.println("print stack trace:");
            ex.printStackTrace();
        };
    }


    /**
     * ——————————————————————————————————————————————
     * 日志监控一,存在潜在问题
     * ——————————————————————————————————————————————
     */
    @Deprecated
    static class LogDecorator implements TaskDecorator {

        @Override
        public Runnable decorate(Runnable runnable) {
            Long requestId = CurrentEnv.requestID();

            System.out.println("requestId:" + requestId +
                    "taskDecorator[step1] >>taskDecorator start, threadName:" + Thread.currentThread().getName());
            try {
                return () -> {
                    try {
                        System.out.println("requestId:" + requestId +
                                "taskDecorator[step2] >>task start, threadName:" + Thread.currentThread().getName());
                        runnable.run();
                        System.out.println("requestId:" + requestId +
                                "taskDecorator[step3] >>task done, threadName:" + Thread.currentThread().getName());
                    } catch (Exception e) {
                        System.out.println("requestId:" + requestId +
                                "taskDecorator[step4] >>task error, threadName:" + Thread.currentThread().getName());
                        throw e;
                    } finally {
                        System.out.println("requestId:" + requestId +
                                "taskDecorator[step5] >>task finally, threadName:" + Thread.currentThread().getName());
                    }
                };
            } catch (Exception e) {
                System.out.println("requestId:" + requestId +
                        " taskDecorator[step6] >>taskDecorator finally, threadName:" + Thread.currentThread().getName());
                return runnable;
            }
        }
    }


    /**
     * ——————————————————————————————————————————————
     * 日志监控二, AsyncUncaughtExceptionHandler日志控制
     * ——————————————————————————————————————————————
     */
    public static void printTaskStarted(Method userDeclaredMethod, Long requestId) {
        System.out.println("request_id[" + requestId + "]");
        System.out.println("——————————————————");
        System.out.println("|async method info|");
        System.out.println("——————————————————————————————————————————————————————————————————————————————");
        System.out.println("async_service       : " + getClassName(userDeclaredMethod.getDeclaringClass()));
        System.out.println("async_method        : " + userDeclaredMethod.getName());
        System.out.println("async_parameterType : " + getClassName(userDeclaredMethod.getParameterTypes()));
        System.out.println("async_annotation    : " + getClassName(Arrays.stream(userDeclaredMethod.getDeclaredAnnotations()).map(Annotation::annotationType).toArray(Class[]::new)));
        System.out.println("async_returnType    : " + getClassName(userDeclaredMethod.getReturnType()));
        System.out.println("async_threwException: " + getClassName(userDeclaredMethod.getExceptionTypes()));
        System.out.println("——————————————————————————————————————————————————————————————————————————————");
    }

    private static String getClassName(Class... classes) {
        return (classes == null || classes.length == 0) ? "null" : Arrays.stream(classes).map(Class::getSimpleName).collect(Collectors.joining(", "));
    }

    //【request空指针异常】提供者
    public static final Supplier<RuntimeException> REQUEST_NULL_POINTER_EXCEPTION_SUPPLIER = () -> new RuntimeException("request is null!");

    public static class RequestContext {
        private static final ThreadLocal<HttpRequestInfo> localRequest = new ThreadLocal<>();

        /**
         * @param once 是否只消费一次request
         */
        public static HttpRequestInfo get(boolean once) {
            HttpRequestInfo httpRequestInfo = localRequest.get();
            if (once) reset();
            return httpRequestInfo;
        }

        public static void set(HttpServletRequest request) {
            localRequest.set(new HttpRequestInfo(request));
        }

        public static void reset() {
            localRequest.remove();
        }
    }

    @Data
    @Accessors(chain = true)
    public static class HttpRequestInfo {
        private String protocol;
        private String requestURI;
        private String method;
        private String queryString;

        public HttpRequestInfo(HttpServletRequest request) {
            this.protocol = "http";
            this.requestURI = request.getRequestURI();
            this.method = request.getMethod();
            this.queryString = request.getQueryString();
        }
    }


    /**
     * ——————————————————————————————————————————————
     * 日志监控三
     * ——————————————————————————————————————————————
     */
    @Bean
    public AsyncMonitor asyncMonitor(){
        return new AsyncMonitor();
    }
}